`timescale 1 ns /  100 ps
module spi_interface
(
	clock,
	resetn,

	fifo_tx_re,
	fifo_tx_empty,
	fifo_tx_readdata,

	fifo_rx_we,
	fifo_rx_full,
	fifo_rx_writedata,

	spi_cs,
	spi_clk,
	spi_mosi,
	spi_miso,

	start,
	read_count,
	done
);

// IO definition
input clock, resetn, start;
input [7:0] read_count;
input fifo_tx_empty;
input [7:0] fifo_tx_readdata;
input fifo_rx_full;
input spi_miso;

output fifo_tx_re, fifo_rx_we;
output [7:0] fifo_rx_writedata;
output spi_cs, spi_clk;
output spi_mosi;
output done;

// state variables
integer IDLE = 0, WORKING = 1, PHASEL = 2, PHASEH = 3,  PHASEDONE = 4, DONE = 5;
reg [2:0] state, next_state;

// variables
reg [7:0] read_count_save; // local copy of read_count
wire read_count_save_load, read_count_save_decrement;
wire tick; // spi clock cycle tick(half cycle)
wire tick_clear;
reg [2:0] bit_count;
wire bit_count_clear, bit_count_increment;
reg [7:0] shift_data;
wire shift_data_load, shift_data_shift, shift_data_clear; 

// state machine outputs
assign read_count_save_load = state == IDLE;
assign spi_cs = ((state == DONE) || (state == IDLE && !start));
assign spi_clk = (state == PHASEH);
assign fifo_tx_re = (state == WORKING && !fifo_tx_empty);
assign tick_clear = (state == WORKING);
assign bit_count_clear = tick_clear;
assign shift_data_load = (state == WORKING) && (!fifo_tx_empty);
assign shift_data_clear = (state == WORKING) && (fifo_tx_empty);
assign shift_data_shift = (state == PHASEH && tick );
assign bit_count_increment = (state == PHASEH && tick == 1);
assign read_count_save_decrement = (state == PHASEDONE && read_count_save != 0);
assign fifo_rx_we = read_count_save_decrement;
assign fifo_rx_writedata = fifo_rx_we ? shift_data : 8'b0;
assign spi_mosi = shift_data[7];
assign done = state == DONE;

// shift register
always @(posedge clock or negedge resetn)
begin
	if (resetn == 0)
		shift_data <= 0;
	else if (shift_data_load)
		shift_data <= fifo_tx_readdata;
	else if (shift_data_clear)
		shift_data <= 8'b0;
	else if (shift_data_shift)
		{shift_data[7:0]} <= {shift_data[6:0], spi_miso};
end

// bit_count register
always @(posedge clock or negedge resetn)
begin
	if (resetn == 0)
		bit_count <= 0;
	else if (bit_count_clear)
		bit_count <= 0;
	else if (bit_count_increment)
		bit_count <= bit_count + 1;
end

// tick_generator
tick_generator spi_tick_generator(.clock(clock), .resetn(resetn), .clear(tick_clear), .tick(tick));
defparam spi_tick_generator.step_size = 20'd65536;

// read_count register
always @(posedge clock or negedge resetn)
begin
	if (resetn == 0)
		read_count_save <= 0;
	else if (read_count_save_load)
		read_count_save <= read_count;
	else if (read_count_save_decrement)
		read_count_save <= read_count_save - 8'b1;
end

// state machine
always @(posedge clock or negedge resetn)
begin
	if (resetn == 0)
		state <= IDLE;
	else
		state <= next_state;
end

always @(*)
begin
	case (state)
		IDLE:
			if (start)
				next_state = WORKING;
			else
				next_state = IDLE;
		WORKING:
			if (fifo_tx_empty && read_count_save == 8'b0)
				// done transmitting
				next_state = DONE;
			else
				next_state = PHASEL;
		PHASEL:
			if (tick)
				next_state = PHASEH;
			else
				next_state = PHASEL;
		PHASEH:
			if (tick)
			begin
				if (bit_count == 3'b111)
					next_state = PHASEDONE;
				else
					next_state = PHASEL;
			end
			else
				next_state = PHASEH;
		PHASEDONE:
			next_state <= WORKING;
		DONE:
			if (start == 0)
				next_state = IDLE;
			else
				next_state = DONE;
		default:
			next_state = IDLE;
	endcase
end

endmodule
